#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<ctype.h>
// 版权所属Achestnut
// Achestnut采用纯c语言自主编写，参考：《编译方法及应用》（许清、刘香芹）
// 2023.5.10于沈阳航空航天大学大学
#define Line_maxsize 1024//行读取最大长度
#define TABLE_SIZE 36//单词表大小
#define KEY_SIZE 10//单词最大长度-1

char lex_table[TABLE_SIZE][KEY_SIZE] = {//符号表
    "无定义",//0
    "标识符",
    "整数",
    "void",//3
    "int",
    "float",
    "char",
    "if",
    "else",
    "while",
    "do",//10
    "for",
    "return",
    "main",
    "+",
    "-",
    "*",
    "/",
    "%",
    "<",
    "<=",//20
    ">",
    ">=",
    "==",
    "!=",
    "&&",
    "||",
    "=",
    "(",
    ")",
    "[",//30
    "]",
    "{",
    "}",
    ";",
    ","
};

char word_list[4*Line_maxsize];//单词输出块

/**************************注释模式**********************************/
bool line_print = 0;//行切换
bool line_note = 0, block_note = 0;//行注释与列注释开关
void start_note(int mode){//开启注释模式
    if(mode == 1) line_note = 1;
    else if(mode == 2)block_note = 1;
}
void end_note(int mode){//关闭注释模式
    if(mode == 1) line_note = 0;
    else if(mode == 2)block_note = 0;
}

/***************************报错处理*********************************/
bool error_flag = 0;//行报错标记
int error_num = 0;//error数
int error_que[Line_maxsize];//报错行存储

void error_in(){//报错入块
    strcpy(word_list + strlen(word_list), "LexicalError,");//报错传到输出块中
    error_num++;//error数量增加
    error_flag = 1;//该行存在报错
}

void error_print(bool flag){//报错提醒输出

    if(!flag)return;//无报错

    printf("LexicalError(s) on line(s) ");
    for(int i = 1; i <= error_num; i++){
        printf("%d,", error_que[i]);
    }
}

/************************基础功能函数********************************/
bool is_letter(char x)//字母判断（未采用）
{
    return (x >= 'a' && x <= 'z') || (x >= 'A' && x <= 'Z');
}

bool is_digit(char x)//数字判断（未采用）
{
    return x >= '0' && x <= '9';
}

long long str_to_int(char* str){//字符串转数字（未采用）
    long long res = 0;
    for(int i =  0; i < (int)strlen(str); i++){
        res *= 10;
        res += str[i] - '0';
    }
    return res;
}

char b_str[Line_maxsize] = {};//二进制串存放
char* int_to_binary(long long x){//整数转二进制字符串
    int len = 0;
    while(x){
        b_str[len++]=char((int)(x&1) + '0');
        x = x>>1;
    }
    b_str[len] = '\0';
    strrev(b_str);//反转
    return b_str;
}

void a_test(){//(未采用)
    //版权所属：Achestnut
}

/******************符号表查询、单词符号序列输出***********************/
void word_inlist(char* temp, int num){//入块
    int end_idx = strlen(word_list);
    word_list[end_idx++] = '<';
    if(num > 9)word_list[end_idx++] = num/10 + '0';//种别码
    word_list[end_idx++] = num%10 + '0';//种别码
    word_list[end_idx++] = ',';
    for(int i = 0; i < (int)strlen(temp); i++){
        word_list[end_idx++] = temp[i];//符号
    }
    word_list[end_idx++] = '>';
    word_list[end_idx++] = ',';
    word_list[end_idx++] = '\0';
}

void check_in(char* temp){//查表并结果入块
    bool flag_in = 0;
    for(int i = 3; i < 36; i++){//查表中除整数以及标识符外的
        //printf("%s, %s, %d, %d\n", temp, lex_table[i], strlen(temp), strlen(lex_table[i]));
        if(!strcmp(temp, lex_table[i])){
            word_inlist((char*)"-", i);
            flag_in = 1;
            break;
        }
    }
    if(!flag_in && isdigit(temp[0])){//整数
        if(strlen(temp) > 1 && temp[0] == '0'){//前导零情况报错
            error_in();
            return;
        }
        word_inlist(int_to_binary(atoll(temp)), 2);//二进制输出变量
        //word_inlist(temp, 2);十进制输出变量
    }
    else if(!flag_in){//标识符
        word_inlist(temp, 1);
    }
}

void print_list(){//块输出
    printf("%s", word_list);
    word_list[0] = '\0';
}


/*****************************各状态转移函数**********************************/

void to_state_12(char *str, int *s_idx, char *temp );//转到12状态
void to_state_11(char *str, int *s_idx, char *temp );//转到11状态
void to_state_10(char *str, int *s_idx, char *temp );//转到10状态
void to_state_9(char *str, int *s_idx, char *temp );//转到9状态
void to_state_8(char *str, int *s_idx, char *temp );//转到8状态
void to_state_7(char *str, int *s_idx, char *temp );//转到7状态
void to_state_6(char *str, int *s_idx, char *temp );//转到6状态
void to_state_5(char *str, int *s_idx, char *temp );//转到5状态
void to_state_4(char *str, int *s_idx, char *temp );//转到4状态
void to_state_3(char *str, int *s_idx, char *temp );//转到3状态
void to_state_2(char *str, int *s_idx, char *temp );//转到2状态
void to_state_1(char *str, int *s_idx, char *temp );//转到1状态
void to_state_0(char *str, int *s_idx, char *temp );//转到0状态

void to_state_12(char *str, int *s_idx, char *temp )//转到12终态
{
    *s_idx = *s_idx + 1;//指针向后走
    end_note(2);//关闭块注释模式
    if(strlen(word_list)){
        print_list();
        line_print = 1;    
    }

}

void to_state_11(char *str, int *s_idx, char *temp )//转到11状态
{
    *s_idx = *s_idx + 1;//指针向后走
    char x = str[*s_idx];
    if(x == '*'){//下一字符为'*'，进入11状态
        to_state_11(str, s_idx, temp );
    }
    else if(x == '/'){//下一字符'/'，转到12状态
        to_state_12(str, s_idx, temp );
    }
    else{//其他字符，转回10状态
        to_state_10(str, s_idx, temp);
    }
}

void to_state_10(char *str, int *s_idx, char *temp )//转到10状态
{
    
    char x = str[*s_idx];
    if(x == '*'){//下一字符为'*'，进入11状态
        to_state_11(str, s_idx, temp );
    }
    else if(x == '#'){//转向下一行
        *s_idx = *s_idx + 1;
        return;
    }
    else{
        *s_idx = *s_idx + 1;//指针向后走
        to_state_10(str, s_idx, temp );//其他字符，转回10状态
    }
}

void to_state_9(char *str, int *s_idx, char *temp)//转到9终态
{
    if(strlen(word_list)){//将注释前的词块输出
        print_list();
        line_print = 1;    
    }
    start_note(1);//开启行注释模式
}

void to_state_8(char *str, int *s_idx, char *temp )//转到8状态
{
    *s_idx = *s_idx + 1;//指针向后走
    char x = str[*s_idx];
    if(x == '/' && !block_note){//下一字符是'/'且不是块模式，转到9终态
        to_state_9(str, s_idx, temp);
    }
    else if(x == '*' && !line_note){//下一字符是'*'且不是行模式，转到10状态
        if(strlen(word_list)){//将注释前的词块输出
            print_list();
            line_print = 1;    
        }
        start_note(2);//开启块注释模式
        *s_idx = *s_idx + 1;//指针向后走
        to_state_10(str, s_idx, temp);
    }
    else{//下一字符是其他，转到2终态
        int should_in = *s_idx - 1;//当前应放入缓存的字符
        int temp_len = strlen(temp);
        temp[temp_len++] = str[should_in];//字符放入
        temp[temp_len] = '\0';
        to_state_2(str, s_idx, temp);
    }
}

void to_state_7(char *str, int *s_idx, char *temp)//转到7状态
{
    *s_idx = *s_idx + 1;//指针向后走
    int should_in = *s_idx - 1;//当前应放入缓存的字符
    int temp_len = strlen(temp);
    temp[temp_len++] = str[should_in];//字符放入
    temp[temp_len] = '\0';
    char x = str[*s_idx];
    if((x == '=' && str[should_in] == '!') || (x == '&' && str[should_in] == '&') || (x == '|' && str[should_in] == '|')){//该位+下一位为"!="或"&&"或"||"，转到6终态
        to_state_6(str, s_idx, temp);
    }
    else{//下一字符是其他，同4终态的错误处理
        error_in();
    }
}

void to_state_6(char *str, int *s_idx, char *temp)//转到6终态
{
    int temp_len = strlen(temp);
    //temp[temp_len++] = str[*s_idx];//前位字符放入
    temp[temp_len++] = str[(*s_idx)++];//该位字符放入;
    temp[temp_len] = '\0';
    check_in(temp);//查表并结果入块
}

void to_state_5(char *str, int *s_idx, char *temp )//转到5状态
{
    *s_idx = *s_idx + 1;//指针向后走
    int should_in = *s_idx - 1;//当前应放入缓存的字符
    int temp_len = strlen(temp);
    temp[temp_len++] = str[should_in];//字符放入
    temp[temp_len] = '\0';
    char x = str[*s_idx];
    if(x == '='){//下一字符是'='，转到6终态
        to_state_6(str, s_idx, temp );
    }
    else{//下一字符是其他，转到2终态
        to_state_2(str, s_idx, temp );
    }
}

void to_state_4(char *str, int *s_idx, char *temp )//转到4终态，变量报错态
{
    *s_idx = *s_idx + 1;//指针向后走
    int should_in = *s_idx - 1;//当前应放入缓存的字符
    int temp_len = strlen(temp);
    temp[temp_len++] = str[should_in];//字符放入，s指针先不用往后走
    temp[temp_len] = '\0';
    char x = str[*s_idx];

    if(isalpha(x) || isdigit(x)){//3状态时指向的是字母，直至不是数字和字母的字符前都是非法
        to_state_4(str, s_idx, temp);
    }
    else{//下一字符是其他，进行报错处理
        error_in();
    }
}

void to_state_3(char *str, int *s_idx, char *temp )//转到3状态
{
    *s_idx = *s_idx + 1;//指针向后走
    int should_in = *s_idx - 1;//当前应放入缓存的字符
    int temp_len = strlen(temp);
    temp[temp_len++] = str[should_in];//字符放入
    temp[temp_len] = '\0';
    char x = str[*s_idx];
    if(isdigit(x)){//下一字符是数字，转回3状态
        to_state_3(str, s_idx, temp);
    }
    else if(isalpha(x)){//下一字符是字母，转到4终态
        to_state_4(str, s_idx, temp);
    }
    else{//其他，转到2终态
        to_state_2(str, s_idx, temp);
    }
}

void to_state_2(char *str, int *s_idx, char *temp )//转到2终态
{
    check_in(temp);//查表并结果入块
}

void to_state_1(char *str, int *s_idx, char *temp )//转到1状态
{
    *s_idx = *s_idx + 1;//指针向后走
    int should_in = *s_idx - 1;//当前应放入缓存的字符
    int temp_len = strlen(temp);
    temp[temp_len++] = str[should_in];//字符放入
    temp[temp_len] = '\0';
    char x = str[*s_idx];
    if(isalpha(x)||isdigit(x)){//下一字符是字母或数字，转回1状态
        to_state_1(str, s_idx, temp);
    }
    else{//下一字符是其他，转到2终态
        to_state_2(str, s_idx, temp);
    }
}

void to_state_0(char *str, int *s_idx, char *temp)//初态 s_idx指向下一字符，其前位才是该放入的字符
{
    //list_error_no();
    char x = str[*s_idx];
    if(strlen(word_list)){//块输出
        print_list();
        line_print = 1;    
    }
         
    if(x == '#'){//13状态，该行结束
        *s_idx = *s_idx + 1;
        return;
    }
    else if(x == ' '){
        *s_idx = *s_idx + 1;
        to_state_0(str, s_idx, temp);//转回0状态
    }
    else if(isalpha(x)){//下一字符是字母，进入1状态
        to_state_1(str, s_idx, temp);
    }
    else if(isdigit(x)){//下一字符是数字，进入3状态
       to_state_3(str, s_idx, temp);
    }
    else if(x == '=' || x == '>' || x == '<'){//下一字符是'='或'<'或'>'，进入5状态
       to_state_5(str, s_idx, temp);
    }
    else if(x == '!' || x == '|' || x == '&'){//下一字符是'!'，进入7状态
       to_state_7(str, s_idx, temp);
    }
    else if(x == '/'){//下一字符是'/'，进入9状态
        to_state_8(str, s_idx, temp);
    }
    else{//下一字符是其他字符，进入2状态
        to_state_6(str, s_idx, temp);
    }
}

/**************************文件读取处理*********************************/
int code_input()//代码文件输入
{
    FILE *fp = fopen("code_str.txt", "w");//打开写入文件
    char x;
    int line_num = 0;
    while(1)
    {
        x = getchar();
        if(x == '\n')line_num++;
        if(x == '#'){//写入结束
            fputc('\n',fp);
            line_num++;
            break;
        }
        fputc(x, fp);
    }
    fclose(fp);//文件关闭    
    return line_num;
}

int temp_getc(FILE *fp, char *temp){//从文件中读出单字符
    *temp = fgetc(fp);
    return *temp;
}

int read_line(FILE *fp, char *str){//从文件中按行读取
    int len = 0;
    char temp;

    while(feof(fp) == 0 && temp_getc(fp, &temp) != '\n'){//当文件结束或读到回车，该行结束
        str[len++] = temp;
    }

    str[len++] = '#';//表示该行结束
    str[len] = '\0';
    return len;//返回改行除去回车外的字符串长度，含空格
}

/*************************词法分析导入**********************************/
void lex_analyz(char* line_str, int len){//以行为单位进行词法分析
    int str_idx = 0;//行中的符号指针，指向要放入缓存的下一位
    char temp[Line_maxsize+1];//单词缓存块
    temp[0] = '\0';
    while(str_idx < len){
        temp[0] = '\0';

        if(line_note){//处于行注释模式，则停止分析该行，关闭行注释模式
            end_note(1);
            break;
        }
        else if(block_note){//处于块注释模式，向后查找块注释结束点
            to_state_10(line_str, &str_idx, temp);
        }
        else{//进行正常词法分析
            to_state_0(line_str, &str_idx, temp);
        }
    }
}

void process_code(int line_num)//代码文件处理
{
    FILE *fp = fopen("code_str.txt", "r");//读取代码文件
    char line_str[Line_maxsize + 1] = {};//行缓存
    int str_len = 0,//行字符长度
        line_scan = 1,//原文件中所处行的位置
        line_now = 0,//处理后所处行的位置
        error_before = 0;//上一行时报错数量

    while(feof(fp) == 0 && line_num >= line_scan++)//按行读取至line_str
    {
        str_len = read_line(fp, line_str);//文件行读取至缓存
    
        lex_analyz(line_str, str_len);//该行进行词法分析

        if(line_print){//若该行有单词输出，则进行换行
            line_print = 0;
            line_now++;
            printf("\n");
        } 

        if(error_flag){//记录该行是否有报错信息
            error_flag = 0;
            while(error_before < error_num){//该行所有报错入队
                error_que[++error_before] = line_now;
            }
        }
    }
}

/****************************主程序*************************************/
int main()
{
    int line_num = code_input();//代码写入，以txt文件形式存储
    process_code(line_num);//代码处理
    error_print(error_num);//报错行提示

    system("pause");

    return 0;
}
